/**
 * LUA 工具模块
 */
#pragma once

#include <lua.h>
#include <lauxlib.h>
#include "transceiver.h"

// 打开 LUA Utils 包
int luaopen_lutils(lua_State* L);

// 在栈上创建一个指定大小的 "cmem" 型 userdata 并返回其指针
void* luaL_newcmem(lua_State* L, size_t size);

#define luaL_printtop(L)						printf("%d\n", lua_gettop(L))
#define luaL_optboolean(L, idx, default_value)	(lua_isnoneornil(L, idx) ? default_value : lua_toboolean(L, idx))
#define luaL_checkboolean(L, idx)				(lua_isnoneornil(L, idx) ? luaL_error(L, "bad argument #%d (boolean expected, got no value)", idx) : lua_toboolean(L, idx))

/**
 * 从参数栈中 idx 位置，检出 buffer 参数，支持以下方式传递参数:
 * 1. xxx(..., buf, dlen, ...): buf 是 lightuserdata 类型的指定的内存，有效数据长度为 dlen（不可为 nil）
 * 2. xxx(..., buf[, dlen, ...]): buf 是 userdata 类型指定的内存，有效数据长度为 dlen; dlen == nil 时, 长度不超过 userdata 本身大小
 * 3. xxx(..., buf[, dlen, ...]): buf 是 string 类型指定的内存，有效数据长度为 dlen; dlen == nil 时, 长度不超过 string 本身大小
 * @param idx: 从参数栈中 idx 位置开始检出参数
 * @param buf: void* 缓冲区指针
 * @param size: size_t 缓冲区本身的大小
 * @param dlen: size_t 有效数据长度
 */
#define luaL_checkbuffer(L, idx, buf, size, dlen) \
	do { \
		if(lua_islightuserdata(L, idx)) { \
			buf = lua_touserdata(L, idx); \
			size = dlen = luaL_checkinteger(L, idx + 1); \
		} else if(lua_isuserdata(L, idx)) { \
			buf = lua_touserdata(L, idx); \
			size = lua_objlen(L, idx); \
			dlen = luaL_optinteger(L, idx + 1, size); \
		} else { \
			buf = (void*)luaL_checklstring(L, idx, &size); \
			dlen = luaL_optinteger(L, idx + 1, size); \
		} \
		if(!buf) luaL_error(L, "Invalid buffer !"); \
		if(size < 1) luaL_error(L, "Invalid buffer size: %d !", size); \
		if(dlen < 1) luaL_error(L, "Invalid data length: %d !", dlen); \
		if(dlen > size) luaL_error(L, "Data length (%d) is beyond buffer size (%d) !", dlen, size); \
	} while(0)

// 获取 tabidx 指定的表中名为 fieldname 的字段的值，转换为 long 后返回
// [-0, +0, m]
static inline long luaL_getfield_long(lua_State* L, int tabidx, const char *fieldname, long default_value) {
	lua_getfield(L, tabidx, fieldname);
	long value = luaL_optlong(L, -1, default_value);
	lua_pop(L, 1);
	return value;
}

// 获取 tabidx 指定的表中名为 fieldname 的字段的值，转换为 double 后返回
// [-0, +0, m]
static inline double luaL_getfield_double(lua_State* L, int tabidx, const char *fieldname, double default_value) {
	lua_getfield(L, tabidx, fieldname);
	double value = luaL_optnumber(L, -1, default_value);
	lua_pop(L, 1);
	return value;
}

// 获取 tabidx 指定的表中第 idx (1-based) 个元素的值，转换为 long 后返回
// [-0, +0, -]
static inline long luaL_array_optlong(lua_State* L, int tabidx, int idx, long default_value) {
	lua_rawgeti(L, tabidx, idx);
	long value = luaL_optlong(L, -1, default_value);
	lua_pop(L, 1);
	return value;
}

// 获取 tabidx 指定的表中第 idx (1-based) 个元素的值，转换为 double 后返回
// [-0, +0, -]
static inline double luaL_array_optdouble(lua_State* L, int tabidx, int idx, double default_value) {
	lua_rawgeti(L, tabidx, idx);
	double value = luaL_optnumber(L, -1, default_value);
	lua_pop(L, 1);
	return value;
}

// 用于 lua_regexp() 的全局变量
extern lua_State* _lua4regexp;

 /**
 * 调用 LUA string.find(str, regexp, 1, false) 匹配字符串
 * @param str 要匹配的字符串
 * @param regexp 正则表达式
 * @return >=0 第一个匹配的位置；<0 匹配失败
 * @attention 调用此函数前，应设置 _lua4regexp 为指定的 LUA 解释器
 */
int lua_regexp(const char* str, const char* regexp);

/**
 * 把一个字符串列表(最后一个字符串要为 NULL)放入 lua 数组中
 * @param idx 数组在栈中的位置索引
 * @param strarr 要放入的字符串列表(最后一个字符串要为 NULL)
 * @return 实际放入数组中的字符串数量
 */
int luaL_fillstringarray(lua_State* L, int idx, const char **strarr);

 // 输出日志
 // _outlog(level, tag, msg)
int _outlog(lua_State* L);

/// 输出二进制
/// _dumpbin(string, len, cpl, show_offset, show_ascii)
/// _dumpbin(userdata, len, cpl, show_offset, show_ascii)
/// _dumpbin(lightuserdata, len, cpl, show_offset, show_ascii)
/// @param len 要打印的长度
/// @param cpl 每行打印多少个字节数据
/// @param show_offset 是否显示每行对应的偏移地址，default true
/// @param show_ascii 是否显示字节对应的 ASCII 码，default true
int _dumpbin(lua_State* L);

// 在栈上创建一个指定大小的 "cmem" 型 userdata，用于存放数据
void* luaL_newcmem(lua_State* L, size_t size);

// 在栈上创建一个指定大小的 "cmem" 型 userdata，用于存放数据
// userdata = _newcmem(size)
int _newcmem(lua_State* L);

// 创建一个指定大小的 string，用于存放数据
// str = _newstrbuf(size)
int _newstrbuf(lua_State* L);

// 分配一块内存，返回其 lightuserdata 指针和大小
// lightuserdata, size = _malloc(size)
int _malloc(lua_State* L);

// 释放 _malloc() 分配的内存 lightuserdata 指针
// _free(lightuserdata)
int _free(lua_State* L);

/// 字符串是否是有不可显示的(非ASCII可显示字符)二进制数据
/// @param str 字符串
/// @param len (可选)长度
/// @return true/false
/// = utils.isbinary("abc")         -- false
/// = utils.isbinary("ab\000cd", 5) -- true
int _isbinary(lua_State* L);

/**
 * 设置/获取 调试标志位
 * preval = _dbgflag(idx, val)
 * curval = _dbgflag(idx)
 * @param idx 第几个标志位。应小于 size_t 的位数。
 * @param bitval 标志位的值 1/true 或 0/false; 为 nil 或 <0 时，返回标志位。
 * @return 设置时返回设置前的值，查询时返回当前值.
 */
int _dbgflag(lua_State* L);

/// 触发调试中断
int _dbgbreak(lua_State* L);

/// Linux 调用 ioctl() 函数, Windows 下暂未实现
/// ioctl(fd, flag, attr)
/// @param fd [int] 文件描述符
/// @param flag [long] 要设置的参数
/// @param attr [int] 其它属性
/// @return 无。遇到错误时抛出 error。
int _ioctl(lua_State* L);

/// 从控制台阻塞/非阻塞地读取一个字符
/// ch = lutils._readstdin(echo, timeout_ms)
/// @param echo [bool] (可选，默认为 false) 是否需要回显输入字符。Linux 下不起作用，总是会回显。
/// @param timeout_ms [long] (可选，默认为 0) >0 表示超时等待时间(ms); =0 表示不等待立即返回; <0 表示永远等待，直至有输入
/// @return ch 有输入时返回输入的字符。无输入时返回 nil。
int _readstdin(lua_State* L);

/// @brief 从控制台非阻塞地读输入一行
/// line, lend = lutils._readline(line)
/// @param line 之前已经有的行数据
/// @return line 行数据
/// @return lend 行是否已经结束
int _readline(lua_State* L);

// 检查 luaL_loadbuffer() 的结果是否是“代码不完整”
bool luaL_isincomplete(lua_State *L, int status);

/// @brief 判断给定的脚本从语法上是否完整，以及语法是否正确，如果完整且正确，则把脚本编译成函数，并压入栈顶
/// @retval STD_ERR_OK 完整且正确，脚本已经编译成函数，并压入栈顶
/// @retval STD_ERR_CONTINUE 代码不完整，需要补充完整
/// @retval 其它错误。
std_err_t luaL_pushtrunk(lua_State *L, const char *script, size_t size);

/// @brief LUA 控制台在 transceiver_t 上开始监听
std_err_t luashell_listen_on(lua_State* L, transceiver_t *trx);
